package com.monkeyboy.manager.http;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.pool.PoolStats;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;

/**
 * 作用： httpclient 工具类
 * 
 * @author Gavin
 * @time:2018年6月12日
 */
@Slf4j
public class HttpUtil {
	private final static int CONNECT_TIMEOUT = 4000;// 连接超时毫秒
	private final static int SOCKET_TIMEOUT = 10000;// 传输超时毫秒
	private final static int REQUESTCONNECT_TIMEOUT = 3000;// 获取请求超时毫秒
	private final static int CONNECT_TOTAL = 200;// 最大连接数
	private final static int CONNECT_ROUTE = 20;// 每个路由基础的连接数
	private final static String ENCODE_CHARSET = "utf-8";// 响应报文解码字符集
	private static PoolingHttpClientConnectionManager connMgr = null;
	private static CloseableHttpClient httpClient = null;
	static {
		ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
		LayeredConnectionSocketFactory sslsf = createSSLConnSocketFactory();
		Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
				.register("http", plainsf).register("https", sslsf).build();
		connMgr = new PoolingHttpClientConnectionManager(registry);
		// 将最大连接数增加到200
		connMgr.setMaxTotal(CONNECT_TOTAL);
		// 将每个路由基础的连接增加到20
		connMgr.setDefaultMaxPerRoute(CONNECT_ROUTE);
		// 可用空闲连接过期时间,重用空闲连接时会先检查是否空闲时间超过这个时间，如果超过，释放socket重新建立
		connMgr.setValidateAfterInactivity(30000);
		// 设置socket超时时间
		SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(SOCKET_TIMEOUT).build();
		connMgr.setDefaultSocketConfig(socketConfig);
		RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(REQUESTCONNECT_TIMEOUT)
				.setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
		HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
			public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
				if (executionCount >= 3) {// 如果已经重试了3次，就放弃
					log.error("重试大于3次，放弃。");
					return false;
				}
				if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接，那么就重试
					log.error("NoHttpResponseException");
					return true;
				}
				if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
					log.error("不要重试SSL握手异常");
					return false;
				}
				if (exception instanceof InterruptedIOException) {// 超时
					log.error("连接超时");
					return true;
				}
				if (exception instanceof UnknownHostException) {// 目标服务器不可达
					log.error("目标服务器不可达");
					return false;
				}
				if (exception instanceof SSLException) {// ssl握手异常
					log.error("ssl握手异常");
					return false;
				}
				HttpClientContext clientContext = HttpClientContext.adapt(context);
				HttpRequest request = clientContext.getRequest();
				// 如果请求是幂等的，就再次尝试
				if (!(request instanceof HttpEntityEnclosingRequest)) {
					return true;
				}
				return false;
			}
		};
		httpClient = HttpClients.custom().setConnectionManager(connMgr).setDefaultRequestConfig(requestConfig)
				.setRetryHandler(httpRequestRetryHandler).build();
		if (connMgr != null && connMgr.getTotalStats() != null) {
			log.info("now client pool " + connMgr.getTotalStats().toString());
		}
	}

	/**
	 * 发送 GET 请求（HTTPS），不带输入数据
	 * 
	 * @param url
	 * @return
	 */
	public static JSONObject doGet(String url) {
		return doGet(url, new HashMap<String, Object>());
	}

	/**
	 * 发送 GET 请求（HTTPS），K-V形式
	 * 
	 * @param url
	 * @param params
	 * @return
	 */
	public static JSONObject doGet(String url, Map<String, Object> params) {
		String apiUrl = url;
		StringBuffer param = new StringBuffer();
		int i = 0;
		for (String key : params.keySet()) {
			if (i == 0)
				param.append("?");
			else
				param.append("&");
			param.append(key).append("=").append(params.get(key));
			i++;
		}
		apiUrl += param;
		String result = null;
		try {
			HttpGet httpGet = new HttpGet(apiUrl);
			HttpResponse response = httpClient.execute(httpGet);
			log.info("请求路径：{}，结果：{}", apiUrl, response);
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				InputStream instream = entity.getContent();
				result = IOUtils.toString(instream, ENCODE_CHARSET);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return JSON.parseObject(result);
	}

	/**
	 * get请求（http格式） header设置
	 * 
	 * @Author:Gavin
	 * @time:2018年6月12日
	 * @param url
	 * @param params
	 * @return
	 */
	public static JSONObject doGetApp(String url, Map<String, String> params, String header) throws Exception {
		String apiUrl = url;
		StringBuffer param = new StringBuffer();
		int i = 0;
		for (String key : params.keySet()) {
			if (i == 0)
				param.append("?");
			else
				param.append("&");
			param.append(key).append("=").append(params.get(key));
			i++;
		}
		apiUrl += param;
		String result = null;
		HttpGet httpGet = new HttpGet(apiUrl);
		httpGet.setHeader("key", header);
		HttpResponse response = httpClient.execute(httpGet);
		log.info("请求路径：{}，结果：{}", url, response);
		HttpEntity entity = response.getEntity();
		if (entity != null) {
			InputStream instream = entity.getContent();
			result = IOUtils.toString(instream, ENCODE_CHARSET);
		}
		return JSON.parseObject(result);
	}

	/**
	 * 发送 POST 请求（HTTP），不带输入数据
	 * 
	 * @param apiUrl
	 * @return
	 */
	public static JSONObject doPost(String apiUrl) {
		return doPost(apiUrl, new HashMap<String, Object>());
	}

	/**
	 * 发送 POST 请求（HTTP），K-V形式
	 * 
	 * @param apiUrl
	 *            API接口URL
	 * @param params
	 *            参数map
	 * @return
	 */
	public static JSONObject doPost(String apiUrl, Map<String, Object> params) {
		String httpStr = null;
		HttpPost httpPost = new HttpPost(apiUrl);
		CloseableHttpResponse response = null;

		try {
			List<NameValuePair> pairList = new ArrayList<>(params.size());
			for (Map.Entry<String, Object> entry : params.entrySet()) {
				NameValuePair pair = new BasicNameValuePair(entry.getKey(), entry.getValue().toString());
				pairList.add(pair);
			}
			httpPost.setEntity(new UrlEncodedFormEntity(pairList, Charset.forName(ENCODE_CHARSET)));
			response = httpClient.execute(httpPost);
			log.info("请求路径：{}，结果：{}", apiUrl, response);
			HttpEntity entity = response.getEntity();
			httpStr = EntityUtils.toString(entity, ENCODE_CHARSET);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (response != null) {
				try {
					EntityUtils.consume(response.getEntity());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return JSON.parseObject(httpStr);
	}

	/**
	 * 发送 POST 请求（HTTP），JSON形式
	 * 
	 * @param apiUrl
	 * @param json
	 *            json对象
	 * @return 返回json格式
	 */
	public static JSONObject doPost(String apiUrl, Object json) {
		String httpStr = null;
		HttpPost httpPost = new HttpPost(apiUrl);
		CloseableHttpResponse response = null;

		try {
			StringEntity stringEntity = new StringEntity(json.toString(), ENCODE_CHARSET);// 解决中文乱码问题
			stringEntity.setContentEncoding(ENCODE_CHARSET);
			stringEntity.setContentType("application/json");
			httpPost.setEntity(stringEntity);
			response = httpClient.execute(httpPost);
			log.info("请求路径：{}，结果：{}", apiUrl, response);
			HttpEntity entity = response.getEntity();
			httpStr = EntityUtils.toString(entity, ENCODE_CHARSET);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (response != null) {
				try {
					EntityUtils.consume(response.getEntity());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return JSON.parseObject(httpStr);
	}

	/**
	 * 作用： 发送xml格式的类型
	 * 
	 * @Author:Gavin
	 * @time:2018年6月25日
	 * @param apiUrl
	 * @param json
	 * @return
	 */
	public static String doPostXml(String apiUrl, String xml) {
		String httpStr = null;
		HttpPost httpPost = new HttpPost(apiUrl);
		CloseableHttpResponse response = null;

		try {
			StringEntity stringEntity = new StringEntity(xml, ENCODE_CHARSET);// 解决中文乱码问题
			stringEntity.setContentEncoding(ENCODE_CHARSET);
			httpPost.setEntity(stringEntity);
			response = httpClient.execute(httpPost);
			log.info("请求路径：{}，结果：{}", apiUrl, response);
			HttpEntity entity = response.getEntity();
			httpStr = EntityUtils.toString(entity, ENCODE_CHARSET);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (response != null) {
				try {
					EntityUtils.consume(response.getEntity());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return httpStr;
	}

	/**
	 * 创建SSL安全连接
	 * 
	 * @return
	 */
	private static SSLConnectionSocketFactory createSSLConnSocketFactory() {
		SSLConnectionSocketFactory sslsf = null;
		// 创建TrustManager() 用于解决javax.net.ssl.SSLPeerUnverifiedException: peer not
		// authenticated
		X509TrustManager trustManager = new X509TrustManager() {
			@Override
			public X509Certificate[] getAcceptedIssuers() {
				return null;
			}

			@Override
			public void checkClientTrusted(X509Certificate[] arg0, String authType) throws CertificateException {
				// TODO Auto-generated method stub
			}

			@Override
			public void checkServerTrusted(X509Certificate[] arg0, String authType) throws CertificateException {
				// TODO Auto-generated method stub
			}
		};
		SSLContext sslContext;
		try {
			sslContext = SSLContext.getInstance(SSLConnectionSocketFactory.TLS);
			sslContext.init(null, new TrustManager[] { (TrustManager) trustManager }, null);
			// 创建SSLSocketFactory , // 不校验域名 ,取代以前验证规则
			sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return sslsf;
	}

	public static Map<HttpRoute, PoolStats> getConnManagerStats() {
		if (connMgr != null) {
			Set<HttpRoute> routeSet = connMgr.getRoutes();
			if (routeSet != null && !routeSet.isEmpty()) {
				Map<HttpRoute, PoolStats> routeStatsMap = new HashMap<HttpRoute, PoolStats>();
				for (HttpRoute route : routeSet) {
					PoolStats stats = connMgr.getStats(route);
					routeStatsMap.put(route, stats);
				}
				return routeStatsMap;
			}
		}
		return null;
	}

	public static PoolStats getConnManagerTotalStats() {
		if (connMgr != null) {
			return connMgr.getTotalStats();
		}
		return null;
	}

	/**
	 * 关闭系统时关闭httpClient
	 */
	public static void releaseHttpClient() {
		try {
			httpClient.close();
		} catch (IOException e) {
			log.error("关闭httpClient异常" + e);
		} finally {
			if (connMgr != null) {
				connMgr.shutdown();
			}
		}
	}

	/**
	 * 测试方法
	 * 
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
	}
}
